/*
 * File     : OpaqueStructureSubBuilder.java
 * Created  : 9 Oct 2011
 *
 * Copyright © 2011 Matthew Wilson (mj. {my-surname} .uk {at} gmail.com)
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
package com.googlecode.dni.internal.structure;

import static com.googlecode.dni.internal.DniInternal.LOGGER;
import static com.googlecode.dni.internal.DniTypes.ABSTRACT_OPAQUE_STRUCTURE_TYPE;
import static com.googlecode.dni.internal.DniTypes.ABSTRACT_STRUCTURE_FACTORY_TYPE;
import static org.objectweb.asm.Opcodes.*;

import java.lang.reflect.Method;
import java.util.List;

import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.MethodVisitor;

import com.googlecode.dni.internal.DniTypes;
import com.googlecode.dni.internal.structure.StructureBuilder.StructureSubBuilder;


/**
 * <p>
 *  Generates byte-code for implementations of structure types, where the
 *  structure is opaque.
 * </p>
 *
 * @author Matthew Wilson
 */
final class OpaqueStructureSubBuilder implements StructureSubBuilder
{

    private final StructureBuilder< ? > parent;


    /**
     * @param parent
     *            the parent
     */
    OpaqueStructureSubBuilder( final StructureBuilder< ? > parent )
    {
        this.parent = parent;
    }

    /** {@inheritDoc} */
    @Override
    public void writeMainClassMethods( final ClassWriter classWriter, final List< Method > methods )
    {
        if ( !methods.isEmpty() )
        {
            for ( Method method : methods )
            {
                LOGGER.warning( "Method : " + method );
            }
            throw new IllegalArgumentException( "Opaque structure must define no fields: "
                                                + this.parent.getType().getName() );
        }

        writePointerConstructor( classWriter );
        this.parent.writeToStringMethod();
    }

    /** {@inheritDoc} */
    @Override
    public void writeStaticInitialiserFragment( final MethodVisitor methodVisitor )
    {
        // nothing
    }

    /** {@inheritDoc} */
    @Override
    public int getLargestFieldUnitSize()
    {
        // no fields => no idea
        return 0;
    }

    @Override
    public String getStructureBaseType()
    {
        return DniTypes.ABSTRACT_OPAQUE_STRUCTURE_TYPE;
    }

    /** {@inheritDoc} */
    @Override
    public String getFactoryBaseType()
    {
        return ABSTRACT_STRUCTURE_FACTORY_TYPE;
    }

    /** {@inheritDoc} */
    @Override
    public void writeAllocateUntypedFactoryMethodImpl( final MethodVisitor methodVisitor )
    {
        methodVisitor.visitTypeInsn( NEW, "java/lang/UnsupportedOperationException" );
        methodVisitor.visitInsn( DUP );
        methodVisitor.visitLdcInsn( "Opaque structure: " + this.parent.getType().getName() );
        methodVisitor.visitMethodInsn( INVOKESPECIAL,
                                       "java/lang/UnsupportedOperationException",
                                       "<init>",
                                       "(Ljava/lang/String;)V" );
        methodVisitor.visitInsn( ATHROW );
    }

    /**
     * Writes a pointer constructor for the opaque case.
     *
     * @param classWriter
     *            the class writer
     */
    private static void writePointerConstructor( final ClassWriter classWriter )
    {
        MethodVisitor methodVisitor = classWriter.visitMethod( ACC_PUBLIC, "<init>", "(J)V", null, null );
        methodVisitor.visitCode();

        // super( pointer, owner );
        methodVisitor.visitVarInsn( ALOAD, 0 );
        methodVisitor.visitVarInsn( LLOAD, 1 );
        methodVisitor.visitMethodInsn( INVOKESPECIAL, ABSTRACT_OPAQUE_STRUCTURE_TYPE, "<init>", "(J)V" );

        // return
        methodVisitor.visitInsn( RETURN );
        methodVisitor.visitMaxs( 0, 0 );
        methodVisitor.visitEnd();
    }

}
